package top.lishuoboy.redis.service.impl;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import top.lishuoboy.redis.service.IdGenerator;

import java.time.Duration;
import java.time.LocalDateTime;

/**
 * <h1>基于redis的分布式唯一id生成器</h1>
 * 基于redis、spring-boot-starter-data-redis
 *
 * @author lishuoboy
 * @date 2024-9-24
 */
@Slf4j
@Component
public class RedisIdGenerator implements IdGenerator {
    public static final int DEFAULT_SEQUENCE_LEN = 8;
    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * <h1>生成id</h1>
     * 优点: long占用存储空间小，数据库索引性能高<br>
     * 缺点：不便于阅读<br>
     *
     * @param key key建议用表名.字段名，如“user_info.id”; 默认为“table_name.id”
     *
     * @return id
     */
    public Long genId(String key) {
        log.debug("开始 生成id, 入参：key=={}", key);
        key = StrUtil.emptyToDefault(key, "table_name.id").toLowerCase();
        key = StrUtil.nullToEmpty(key).toLowerCase();
        Long id = redisTemplate.opsForHash().increment("table.id", key, 1);
        log.debug("结束 生成id, id=={}, key=={}", id, key);
        return id;
    }


    /**
     * @param key         key建议用表名.字段名，如“user_info.id”; 默认为“table_name.id”
     * @param pre         id前缀，建议用表名缩写，user_info表用“UI”，默认为空
     * @param dateFormat  id日期格式，默认yyyyMMdd
     * @param sequenceLen id序列号长度，默认8
     *
     * @return id（pre + 格式为dateFormat的日期 + 长度为sequenceLen的数字）
     */
    public String genIdStr(String key, String pre, DateFormatEnum dateFormat, Integer sequenceLen) {
        log.debug("开始 生成id, 入参：key=={}, pre=={}, dateFormat=={}, sequenceLen=={}", key, pre, dateFormat, sequenceLen);
        key = StrUtil.emptyToDefault(key, "table_name.id").toLowerCase();
        pre = StrUtil.nullToEmpty(pre);
        dateFormat = ObjUtil.defaultIfNull(dateFormat, DateFormatEnum.yyyyMMdd);
        sequenceLen = sequenceLen == null ? DEFAULT_SEQUENCE_LEN : sequenceLen;

        String date = LocalDateTimeUtil.format(LocalDateTime.now(), dateFormat.name());
        key = "table.id:" + key + ":" + date;
        Long value = redisTemplate.opsForValue().increment(key);
        if (1L == value) { // 设置过期时间
            Duration expireTime = calcExpireTime(dateFormat).plusHours(1);  //考虑到应用服务器可能存在时间差，过期时间多保存1小时（也可以使用id服务器时间的方案，增加复杂读没必要）;
            ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
            valueOperations.set(key, value.toString(), expireTime);
        }
        String sequence = StrUtil.fillBefore(String.valueOf(value), '0', sequenceLen);
        String id = pre + date + sequence;
        log.debug("结束 生成id, id=={}, key=={}", id, key);
        return id;
    }


    /**
     * @param key         key建议用表名.字段名，如“user_info.id”; 默认为“table_name.id”
     * @param pre         id前缀，建议用表名缩写，user_info表用“UI”，默认为空
     * @param sequenceLen id序列号长度，默认8
     *
     * @return id（pre + 格式为dateFormat的日期 + 长度为sequenceLen的数字）
     */
    public String genIdStr(String key, String pre, Integer sequenceLen) {
        return genIdStr(key, pre, DateFormatEnum.yyyyMMdd, sequenceLen);
    }

    /**
     * @param key key建议用表名.字段名，如“user_info.id”; 默认为“table_name.id”
     * @param pre id前缀，建议用表名缩写，user_info表用“UI”，默认为空
     *
     * @return id（pre + 格式为dateFormat的日期 + 长度为sequenceLen的数字）
     */
    public String genIdStr(String key, String pre) {
        return genIdStr(key, pre, DateFormatEnum.yyyyMMdd, DEFAULT_SEQUENCE_LEN);
    }
}
